package sk.hackcraft.util.simulation;

import java.util.PriorityQueue;
import sk.hackcraft.util.simulation.statistics.ReplicationReport;

public abstract class Replication
{
	private double currentTime;
	private final double replicationLength;
	
	private final PriorityQueue<Event> eventCalendar;
	
	private SimulationController controller;
	private ControlItem control;
	
	public Replication(double replicationLength)
	{
		this.currentTime = 0;
		this.eventCalendar = new PriorityQueue<Event>();
		
		this.replicationLength = replicationLength;
		
		this.control = new ControlItem();
	}
	
	protected void setController(SimulationController controller)
	{
		this.controller = controller;
		this.control.setController(controller);
	}
	
	protected SimulationController getController()
	{
		return controller;
	}
	
	public double getCurrentTime()
	{
		return this.currentTime;
	}
	
	public void planEvent(Event event)
	{
		event.setReplication(this);
		this.eventCalendar.add(event);
	}
	
	private void planSystemEvent()
	{
		planEvent(new SystemEvent(getCurrentTime()));
	}
	
	private void eventAction()
	{
		Event event = this.eventCalendar.poll();
		this.currentTime = event.getTime();
		event.execute();
	}
	
	private void systemAction()
	{
		if(controller != null)
		{
			if(controller.isCollecting())
			{
				controller.reportReplication(getResults());
			}

			if(controller.canPlanSystemEvent())
			{
				planSystemEvent();
			}
		}
	}
	
	public void simulate()
	{
		reset();
		
		while(hasEvent() && !limitExceeded() && !control.isStopped())
		{
			if(!control.isPaused())
			{
				eventAction();
				systemAction();
			}
			else
			{
				sleepAction();
			}
			
			control.updateState();
		}
		
		afterRun();
	}
	
	private void sleepAction()
	{
		try
		{
			Thread.sleep(100);
		}
		catch (InterruptedException ex)
		{
		}
	}
	
	private void reset()
	{
		eventCalendar.clear();
		currentTime = 0;
		
		if(this.controller != null)
		{
			controller.setSystemEventExists(false);
			
			planEvent(new Event(getCurrentTime()){
				@Override
				public void execute() { }
			});
		}
		
		control.updateState();
		
		beforeRun();
	}
	
	public boolean hasEvent()
	{
		return this.eventCalendar.size() > 0;
	}
	
	private boolean limitExceeded()
	{
		return currentTime >= replicationLength;
	}

	public double getReplicationLength()
	{
		return replicationLength;
	}

	protected abstract void beforeRun();
	protected abstract void afterRun();
	
	public abstract ReplicationReport getResults();
}
